﻿namespace MicroCloud.AspNetCore
{
    /// <summary>
    /// 业务实现：国际化(I18N)处理程序
    /// </summary>
    public class I18nHandler : II18nHandler
    {
        /// <summary>
        /// 初始化一个国际化(I18N)处理程序 <see cref="I18nHandler"/> 的新实例
        /// </summary>
        /// <param name="provider">服务提供者</param>
        public I18nHandler(IServiceProvider provider)
        {
            ServiceProvider = provider;
            Initialize();
        }

        #region 静态
        /// <summary>
        /// 获取 物理文件提供程序
        /// </summary>
        protected static PhysicalFileProvider FileProvider { get; private set; }

        /// <summary>
        /// 获取 区域语系集合
        /// </summary>
        protected static IList<Locale> Locales { get; private set; } = Locale.Locales.ToList();

        /// <summary>
        /// 获取 国际化(I18N)资源集合
        /// </summary>
        protected static IDictionary<LangType, IDictionary<string, string>> Resources { get; private set; } = new Dictionary<LangType, IDictionary<string, string>>();

        #endregion

        #region 属性
        /// <summary>
        /// 获取 服务提供者对象
        /// </summary>
        protected IServiceProvider ServiceProvider { get; }

        /// <summary>
        /// 获取 日志对象
        /// </summary>
        protected ILogger Logger => ServiceProvider.GetLogger(GetType());

        /// <summary>
        /// 获取 当前HttpContext访问对象
        /// </summary>
        protected IHttpContextAccessor HttpContextAccessor => ServiceProvider.GetService<IHttpContextAccessor>();

        #endregion

        #region II18nHandler
        /// <summary>
        /// 获取 资源物理路径
        /// </summary>
        public string ResourcePath => Path.Combine(DirectoryHelper.RootPath(), PathConstants.LocalizationPath);

        /// <summary>
        /// 获取 默认语言
        /// </summary>
        public string DefaultLanguage => ServiceProvider.GetMicroCloudOptions()?.Mvc?.DefaultLanguage;

        /// <summary>
        /// 获取国际化(I18N)资源集合
        /// </summary>
        /// <returns>国际化(I18N)资源集合</returns>
        public IDictionary<LangType, IDictionary<string, string>> GetResources()
        {
            return Resources;
        }

        /// <summary>
        /// 获取指定语言类型的国际化(I18N)资源
        /// </summary>
        /// <param name="langType">语言类型</param>
        /// <returns>国际化(I18N)资源</returns>
        public IDictionary<string, string> GetResource(LangType langType)
        {
            if (Resources.TryGetValue(langType, out IDictionary<string, string> value))
            {
                return value;
            }
            return new Dictionary<string, string>();
        }

        /// <summary>
        /// 获取国际化(I18N)字符
        /// </summary>
        /// <param name="key">字符键</param>
        /// <param name="locale">区域语系</param>
        /// <returns>国际化(I18N)字符</returns>
        public string Get(string key, Locale locale = null)
        {
            string value = string.Empty;
            if (key.IsMissing()) return value;

            locale ??= GetLocale();
            if (locale == null) return value;

            value = GetValue(locale.LangType, key);
            return value;
        }

        /// <summary>
        /// 增加国际化(I18N)字符
        /// </summary>
        /// <param name="key">资源键</param>
        /// <param name="value">资源值</param>
        /// <param name="locale">区域语系</param>
        public void Add(string key, string value, Locale locale = null)
        {
            if (key.IsMissing() || value.IsMissing())
            {
                return;
            }
            locale ??= GetLocale();
            if (locale != null)
            {
                AddResource(locale, key, value);
            }
        }

        #endregion

        #region 私有方法
        /// <summary>
        /// 初始化
        /// </summary>
        protected virtual void Initialize()
        {
            DirectoryHelper.CreateIfNotExists(ResourcePath);
            InitializeResources(Locale.Locales);

            //定义一个C:\Users\liuzh\MyBox\TestSpace目录的FileProvider
            //FileProvider = new PhysicalFileProvider(ResourcePath);

            //foobar/data.txt：存储在目录foobar下的文件data.txt。
            //foobar/*.txt：存储在目录foobar下的所有.txt文件。
            //foobar/*.*：存储在目录foobar下的所有文件。
            //foobar//*.*：存储在目录foobar的所有子目录下的所有文件。

            //让这个Provider开始监听这个目录下的所有文件
            //var changeToken = phyFileProvider.Watch("*.*");
            //changeToken.RegisterChangeCallback(_ => { Console.WriteLine("老鼠被蛇吃"); }, new object());
        }

        /// <summary>
        /// 初始化国际化(I18N)资源
        /// </summary>
        /// <param name="locales">区域语系集合</param>
        protected virtual void InitializeResources(params Locale[] locales)
        {
            if (locales != null)
            {
                foreach (var locale in locales)
                {
                    var fileName = GetFileName(locale);
                    InitializeResource(locale);
                    if (FileProvider != null)
                    {
                        ChangeToken.OnChange(() => FileProvider.Watch(fileName), () => { InitializeResource(locale); });
                    }
                }
            }
        }

        /// <summary>
        /// 初始化国际化(I18N)资源
        /// </summary>
        /// <param name="locale">区域语系</param>
        protected virtual void InitializeResource(Locale locale)
        {
            var resource = ReadResourceFile(locale);
            if (resource != null)
            {
                if (Resources.ContainsKey(locale.LangType))
                {
                    Resources[locale.LangType] = resource;
                }
                else
                {
                    Resources.Add(locale.LangType, resource);
                }
            }
        }

        /// <summary>
        /// 读取国际化(I18N)资源文件
        /// </summary>
        /// <param name="locale">区域语系</param>
        /// <param name="retryIfFailed">如果失败，则重试</param>
        /// <returns>Key-Value字典</returns>
        protected virtual Dictionary<string, string> ReadResourceFile(Locale locale, bool retryIfFailed = true)
        {
            Dictionary<string, string> resource = null;
            var filePath = GetFilePath(locale);
            if (File.Exists(filePath))
            {
                try
                {
                    using StreamReader sr = new(filePath);
                    JsonTextReader jtr = new(sr);
                    JsonSerializer se = new();
                    object obj = se.Deserialize(jtr, typeof(Dictionary<string, string>));
                    resource = (Dictionary<string, string>)obj;
                }
                catch (IOException ioEx)
                {
                    if (retryIfFailed)
                    {
                        Logger.LogDebug(ioEx, "读取 {filePath} 本地化文件错误，将在等待文件未被占用后重新尝试。", filePath);
                        var fileIsUsed = FileHelper.FileIsUsed(filePath);
                        while (fileIsUsed)
                        {
                            Thread.Sleep(TimeSpan.FromSeconds(1));
                            fileIsUsed = FileHelper.FileIsUsed(filePath);
                        }
                        ReadResourceFile(locale, false);
                    }
                    else
                    {
                        Logger.LogError(ioEx, "读取 {filePath} 本地化文件错误，操作终止。", filePath);
                        return null;
                    }
                }
                catch (System.Exception ex)
                {
                    Logger.LogError(ex, "读取 {filePath} 文件出错，请检查文件内容格式。", filePath);
                    return null;
                }
            }
            return resource;
        }

        /// <summary>
        /// 获取区域语系
        /// </summary>
        /// <returns>区域语系</returns>
        protected virtual Locale GetLocale()
        {
            Locale locale = null;
            if (HttpContextAccessor?.HttpContext != null)
            {
                var acceptLanguages = HttpContextAccessor.HttpContext.Request.AcceptLanguages();
                if (acceptLanguages?.Length > 0)
                {
                    locale = Locales.FirstOrDefault(o => acceptLanguages.Contains(o.ToString(), StringComparer.OrdinalIgnoreCase));
                }
            }
            locale ??= Locales.FirstOrDefault(o => DefaultLanguage.ToUpper() == o.ToString().ToUpper());
            return locale;
        }

        /// <summary>
        /// 追加国际化(I18N)资源
        /// </summary>
        /// <param name="locale">区域语系</param>
        /// <param name="key">资源键</param>
        /// <param name="value">资源值</param>
        protected virtual void AddResource(Locale locale, string key, string value)
        {
            if (locale == null || key.IsMissing() || value.IsMissing())
            {
                return;
            }

            var resource = GetResource(locale.LangType) ?? new Dictionary<string, string>();
            if (resource.ContainsKey(key))
            {
                resource.Remove(key);
            }
            resource.Add(key, value);

            if (Resources.ContainsKey(locale.LangType))
            {
                Resources[locale.LangType] = resource;
            }
            else
            {
                Resources.Add(locale.LangType, resource);
            }

            //WriteJsonFile(locale, resource);
            var t1 = new Task(() => WriteJsonFile(locale, resource));
            t1.Start();
            //Task.WaitAll(t1);//等待所有任务结束
        }

        /// <summary>
        /// 将序列化的json字符串内容写入Json文件，并且保存
        /// </summary>
        /// <param name="locale">区域语系</param>
        /// <param name="resource">资源键值对集合</param>
        /// <param name="retryIfFailed">如果失败，则重试</param>
        protected virtual Task WriteJsonFile(Locale locale, IDictionary<string, string> resource, bool retryIfFailed = true)
        {
            if (locale == null || resource == null || resource?.Count <= 0)
            {
                return Task.CompletedTask;
            }
            resource = resource.OrderBy(o => o.Key).ToDictionary(p => p.Key, o => o.Value);
            var jsonFilePath = GetFilePath(locale);
            FileStream fileStream = null;
            try
            {
                fileStream = File.Open(jsonFilePath, FileMode.Create, FileAccess.Write, FileShare.Write);
            }
            catch (IOException ioEx)
            {
                if (retryIfFailed)
                {
                    Logger.LogDebug(ioEx, "打开 {jsonFilePath} 本地化文件准备写入错误，将在等待文件未被占用后重新尝试。", jsonFilePath);
                    var fileIsUsed = FileHelper.FileIsUsed(jsonFilePath);
                    while (fileIsUsed)
                    {
                        Thread.Sleep(TimeSpan.FromSeconds(1));
                        fileIsUsed = FileHelper.FileIsUsed(jsonFilePath);
                    }
                    WriteJsonFile(locale, resource, false);
                }
                else
                {
                    Logger.LogError(ioEx, "打开 {jsonFilePath} 本地化文件准备写入错误，写入操作终止。", jsonFilePath);
                }
            }

            using StreamWriter sw = new(fileStream);
            using (JsonWriter jw = new JsonTextWriter(sw))
            {
                jw.Formatting = Formatting.Indented;
                JsonSerializer serializer = new();
                serializer.Serialize(jw, resource);
            }

            fileStream?.Dispose();
            return Task.CompletedTask;
        }
 
        /// <summary>
        /// 获取指定本地化资源的节点信息
        /// </summary>
        /// <param name="langType">语言类型</param>
        /// <param name="key">键值</param>
        /// <returns>本地显示</returns>
        protected virtual string GetValue(LangType langType, string key)
        {
            var value = string.Empty;
            if (Resources.TryGetValue(langType, out IDictionary<string, string> resource))
            {
                if (resource.ContainsKey(key))
                {
                    value = resource[key];
                }
            }
            return value;
        }

        /// <summary>
        /// 获取指定语言类型的资源文件名称
        /// </summary>
        /// <param name="locale">语言类型</param>
        /// <returns>资源文件名称</returns>
        protected virtual string GetFileName(Locale locale)
        {
            //var fileName = locale == null ? null : $"Lang.{locale.ToString()}.json";
            var fileName = locale == null ? null : $"{locale.ToString()}.json";
            return fileName;
        }

        /// <summary>
        /// 获取指定语言类型的资源文件路径
        /// </summary>
        /// <param name="locale">语言类型</param>
        /// <param name="createIfNotExist">如果没有就创建文件</param>
        /// <returns>资源文件路径</returns>
        protected virtual string GetFilePath(Locale locale, bool createIfNotExist = true)
        {
            var filePath = locale == null ? null : Path.Combine(ResourcePath, GetFileName(locale));
            if (!filePath.IsMissing() && createIfNotExist)
            {
                if (!File.Exists(filePath))
                {
                    File.Create(filePath).Dispose();
                }
            }
            return filePath;
        }

        #endregion
    }
}